/*
 * Copyright 2016 qyh.me
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package me.qyh.blog.file;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.List;
import java.util.function.Supplier;

import javax.imageio.ImageIO;

import org.im4java.core.ConvertCmd;
import org.im4java.core.IM4JavaException;
import org.im4java.core.IMOperation;
import org.im4java.core.IdentifyCmd;
import org.im4java.core.ImageCommand;
import org.im4java.core.Operation;
import org.im4java.process.ArrayListOutputConsumer;
import org.im4java.process.ProcessStarter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;

import com.madgag.gif.fmsware.GifDecoder;

import me.qyh.blog.exception.SystemException;
import me.qyh.blog.util.FileUtils;
import me.qyh.blog.util.Validators;

/**
 * 图片处理类，基于{@link http://www.graphicsmagick.org/}，可以用来处理PNG,JPEG,GIF,WEBP等多种格式
 * 
 * @see http://im4java.sourceforge.net/docs/dev-guide.html
 * @author Administrator
 *
 */
public class GraphicsMagickImageHelper extends ImageHelper implements InitializingBean {

	private static final Logger LOGGER = LoggerFactory.getLogger(GraphicsMagickImageHelper.class);

	/**
	 * 在windows环境下，必须设置这个路径
	 */
	private String magickPath;

	private static final boolean WINDOWS = File.separatorChar == '\\';

	/**
	 * 如果为true，那么将会以渐进的方式显示出来，但在有些浏览器，例如EDGE则会先显示空白后再显示图片
	 */
	private boolean doInterlace = true;

	@Override
	protected void doResize(Resize resize, File src, File dest) throws IOException {
		IMOperation op = new IMOperation();
		op.addImage();
		setResize(resize, op);
		String ext = FileUtils.getFileExtension(dest.getName());
		if (!maybeTransparentBg(ext)) {
			setWhiteBg(op);
		}
		if (interlace(dest)) {
			op.interlace("Line");
		}
		addCompressOp(op, ext);
		op.addImage();

		File temp = FileUtils.appTemp(ext);
		run(op, src.getAbsolutePath(), temp.getAbsolutePath());
		FileUtils.move(temp, dest);
	}

	@Override
	protected ImageInfo doRead(File file) throws IOException {
		IMOperation localIMOperation = new IMOperation();
		localIMOperation.ping();
		localIMOperation.format("%w\n%h\n%m\n");
		localIMOperation.addImage();
		ArrayListOutputConsumer localArrayListOutputConsumer = new ArrayListOutputConsumer();
		run(() -> {
			IdentifyCmd localIdentifyCmd = new IdentifyCmd(true);
			localIdentifyCmd.setOutputConsumer(localArrayListOutputConsumer);
			return localIdentifyCmd;
		}, localIMOperation, file.getAbsolutePath());

		List<String> atts = localArrayListOutputConsumer.getOutput();
		Iterator<String> it = atts.iterator();
		return new ImageInfo(Integer.parseInt(it.next()), Integer.parseInt(it.next()), it.next());
	}

	@Override
	protected void doCompress(File src, File dest) throws IOException {
		String srcExt = FileUtils.getFileExtension(src.getName());
		if (isGIF(srcExt)) {
			File _gif = FileUtils.appTemp(GIF);
			IMOperation op = new IMOperation();
			op.addImage();
			op.strip();
			op.p_profile("*");
			op.addImage();
			try {
				run(op, src.getAbsolutePath(), _gif.getAbsolutePath());
			} catch (IOException e) {
				LOGGER.debug("GraphicsMagick无法获取" + src.getAbsolutePath() + "这张图片的封面，尝试用GifDecoder来获取", e);
				getGifCoverUseJava(src, _gif);
			}
			src = _gif;
		}
		String ext = FileUtils.getFileExtension(dest.getName());
		IMOperation op = new IMOperation();
		op.addImage();
		if (!maybeTransparentBg(ext) || !maybeTransparentBg(srcExt)) {
			setWhiteBg(op);
		}
		op.strip();
		addCompressOp(op, ext);
		op.addImage();

		File temp = FileUtils.appTemp(ext);
		run(op, src.getAbsolutePath(), temp.getAbsolutePath());
		FileUtils.move(temp, dest);
	}

	private void addCompressOp(IMOperation op, String ext) {
		if (isJPEG(ext)) {
			op.interlace("Plane");
			op.quality(85D);
		}
		if (isPNG(ext)) {
			op.define("png:compression-filter=2");
			op.define("png:compression-level=9");
			op.define("png:compression-strategy=1");
		}
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		if (WINDOWS) {
			if (Validators.isEmptyOrNull(magickPath, true)) {
				throw new SystemException("windows下必须设置GraphicsMagick的主目录");
			}
			ProcessStarter.setGlobalSearchPath(magickPath);
		}
	}

	private boolean interlace(File dest) {
		if (!doInterlace) {
			return false;
		}
		String ext = FileUtils.getFileExtension(dest.getName());
		return isGIF(ext) || isPNG(ext) || isJPEG(ext);
	}

	private void getGifCoverUseJava(File gif, File _gif) throws IOException {
		GifDecoder gd = new GifDecoder();
		try (InputStream is = new FileInputStream(gif)) {
			int flag = gd.read(is);
			if (flag != GifDecoder.STATUS_OK) {
				throw new IOException(gif + "文件无法获取封面");
			}
			BufferedImage bi = gd.getFrame(0);
			ImageIO.write(bi, GIF, _gif);
		}
	}

	private void setWhiteBg(IMOperation op) {
		op.background("rgb(255,255,255)");
		op.extent(0, 0);
		op.addRawArgs("+matte");
	}

	public void setDoInterlace(boolean doInterlace) {
		this.doInterlace = doInterlace;
	}

	public void setMagickPath(String magickPath) {
		this.magickPath = magickPath;
	}

	protected void setResize(Resize resize, IMOperation op) {
		if (resize.getSize() != null) {
			op.thumbnail(resize.getSize(), resize.getSize(), '>');
		} else {
			if (!resize.isKeepRatio()) {
				op.thumbnail(resize.getWidth(), resize.getHeight(), '!');
			} else {
				if (resize.getWidth() <= 0) {
					op.thumbnail(Integer.MAX_VALUE, resize.getHeight(), '>');
				} else if (resize.getHeight() <= 0) {
					op.thumbnail(resize.getWidth(), Integer.MAX_VALUE, '>');
				} else {
					op.thumbnail(resize.getWidth(), resize.getHeight(), '>');
				}
			}
		}
	}

	private void run(Supplier<ImageCommand> supplier, Operation operation, Object... args) throws IOException {
		try {
			supplier.get().run(operation, args);
		} catch (InterruptedException e) {
			Thread.currentThread().interrupt();
			throw new SystemException(e.getMessage(), e);
		} catch (IM4JavaException e) {
			throw new IOException(e);
		}
	}

	private void run(Operation operation, Object... args) throws IOException {
		run(() -> new ConvertCmd(true), operation, args);
	}

	@Override
	public boolean supportWebp() {
		return true;
	}

}
